import abc
import pandas as pd
from collections import namedtuple

class Algo(object):
    def __init__(self, name=None):
        self._name = name

    def __str__(self):
        return str(self.__class__.__name__)

    @property
    def name(self):
        """
        Algo name.
        """
        if self._name is None:
            self._name = self.__class__.__name__
        return self._name

    def __call__(self, target):
        raise NotImplementedError("%s not implemented!" % self.name)


class RunOnce(Algo):
    def __init__(self):
        super(RunOnce, self).__init__()
        self.done = False



    def __call__(self, context):
       done = self.done
       self.done = True
       return done


class RunPeriod(Algo):
    def __init__(self,period):
        super(RunPeriod, self).__init__()
        self.period = period

    def __call__(self, context):
        if context['index'] % self.period:
            return True #done，不继续
        return False

class SelectAll(Algo):
    def __init__(self):
        super().__init__()

    def __call__(self, context):
        df_bar = context['bar']
        context['selected'] = list(df_bar.index)

class SelectFix(Algo):
    def __init__(self, instruments):
        super(SelectFix, self).__init__()
        self.instruments = instruments

    def __call__(self, context):
        df_bar = context['bar']
        #固定的instruments，可以是df_bar的子集； 而df_bar的index也可能是instruments的子集
        #所以这里取交集
        context['selected'] = list(set(df_bar.index).intersection(set(self.instruments)))
        return False



Signal = namedtuple('signal',['signals','max_match_count'])
Sorter = namedtuple('sorter',['sorter','ascending','max_hold'])
class SelectWhere(Algo):
    def __init__(self,buy_signal=Signal(signals=[],max_match_count=-1),sell_signal=Signal(signals=[],max_match_count=-1),sorter=None):
        super().__init__()
        self.buy_signal = buy_signal
        self.sell_signal = sell_signal
        self.sorter = sorter

    def check_rules(self,bar,signal):
        count = 0
        rules = signal.signals
        for r in rules:
            #信号为True
            if bar[r]:
                count += 1

        if signal.max_match_count > 0:
            if count >= signal.max_match_count:
                return True
        else:# -1就是匹配全部
            if count == len(rules):
                return True
        return False

    def __call__(self, context):
        config = context['engine'].config
        universe = config.universe
        df_bar = context['bar']
        s = context['strategy']
        buy = []
        sell = []
        holding = s.acc.get_holding_list()
        # 从config里读出universe
        for s in universe:
            bar = df_bar.loc[s]
            if self.check_rules(bar, self.buy_signal):
                buy.append(s)
            if self.check_rules(bar, self.sell_signal):
                sell.append(s)

        #已持仓+待买入，去除待卖出= 将要持仓
        be_selected = list(set((holding + buy)))
        to_be_selected = []
        for s in be_selected:
            if s not in sell:
                to_be_selected.append(s)




        if selected == holding:
            return True


        context['selected'] = selected
        return False

class SelectBySignal(Algo):
    def __init__(self, signal_buy='to_buy', signal_sell='to_sell'):
        super(SelectBySignal, self).__init__()
        self.signal_buy = signal_buy
        self.signal_sell = signal_sell

    def __call__(self, context):
        bar = context['bar'].copy()

        # 已经持仓的
        acc = context['acc']
        holding = acc.get_holding_instruments()
        # 再根据两个信号列，buy的选入，sell的去除

        to_buy = list(bar[bar[self.signal_buy]].index)
        to_sell = list(bar[bar[self.signal_sell]].index)

        instruments = to_buy + holding
        to_selected = []
        for s in instruments:
            if s not in to_sell:
                to_selected.append(s)

        '''
        
        if len(to_selected):
            print(context['date'],'当前已持仓或需买入',to_selected)
        else:
            print(context['date'], '信号空仓', to_selected)
        '''
        context['selected'] = to_selected

class SelectTopK(Algo):
    def __init__(self, K=2, col='pred_score'):
        super().__init__()
        self.K = K
        self.col = col

    def __call__(self, context):
        bar = context['bar'].copy()
        #先看selected
        if 'selected' in context.keys():
            if len(context['selected']) == 0:
                #print('SelectTopK遇空仓，直接跳过')
                # 若前面计算过selected,是空仓，那不需要排序，继续下一轮，但不退出——有可能要清仓操作。
                return False

            to_select = []
            for s in context['selected']:
                if s in bar.index:
                    to_select.append(s)

            bar = bar.loc[to_select]

        bar.sort_values(by=self.col, ascending=False, inplace=True) #倒序
        symbols = bar.index[:self.K]
        context['selected'] = symbols



class WeightFix(Algo):
    def __init__(self, weights):
        super().__init__()
        self.weights = weights
    def __call__(self, context):
        selected = context["selected"]
        n = len(selected)
        if n != len(self.weights):
            print('权重与标的长度不一致')
            return
        context['weights'] = {code:self.weights[i] for i,code in enumerate(selected)}

class WeightEqually(Algo):
    def __init__(self):
        super().__init__()

    def __call__(self, context):
        selected = context["selected"]
        n = len(selected)
        if n > 0:
            context['weights'] = {code:1/n for code in selected}
        else:
            context['weights'] = {}
        return False